package com.m4399.minigamedemo

import android.os.Build
import android.util.Log
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier

/**
 *
 * 反射的属性和方法的扩展
 *
 * @author  LinZhiWei
 * @date    2020年09月29日 16:10
 *
 * Copyright (c) 2020年, 4399 Network CO.ltd. All Rights Reserved.
 */
val fieldCache = HashMap<Int, Field>()
val getField: Method = Class::class.java.getDeclaredMethod("getField", String::class.java)
val getDeclaredField: Method = Class::class.java.getDeclaredMethod("getDeclaredField", String::class.java)

@Throws(NoSuchFieldException::class)
fun Any.get(fieldName: String): Any? {
    val field = findField(this.javaClass, fieldName)!!
    return field.get(this)
}

fun <T> Class<*>.get(fieldName: String): T? {
    val field = findField(this, fieldName)!!
    return field.get(this) as? T
}

@Throws(NoSuchFieldException::class)
fun Any.set(fieldName: String, value: Any) {
    findField(this.javaClass, fieldName)?.let {
        handleFinal(it)
        it.set(this, value)
    }
}

fun Class<*>.set(fieldName: String, value: Any) {
    findField(this, fieldName)?.let {
        handleFinal(it)
        it.set(this, value)
    }
}

fun handleFinal(field: Field) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        findField(field.javaClass, "accessFlags")?.run {
            isAccessible = true
            val modifier = get(field) as Int
            if ((modifier and Modifier.FINAL) == Modifier.FINAL) {
                set(field, modifier and Modifier.FINAL.inv())
            }
        }
    }
}

/**
 * Locate a given field anywhere in the class inheritance hierarchy.
 *
 * @param clazz an clazz to search the field into.
 * @param name  field name
 * @return a field object
 * @throws NoSuchFieldException if the field cannot be located
 */
@Throws(NoSuchFieldException::class)
fun findField(clazz_: Class<*>?, name: String): Field? {
    var clazz = clazz_
    while (clazz != null) {
        val key = clazz.hashCode() xor name.hashCode()
        var field: Field? = fieldCache[key]
        if (field != null && name == field.name) {
            return field
        }
        try {
            field = clazz.getDeclaredField(name)
        } catch (e: NoSuchFieldException) {
            // ignore and search next
        }

        // for sdk hide api
        if (field == null) {
            try {
                field = clazz.getField(name)
            } catch (e: NoSuchFieldException) {
                // ignore and search next
            }
        }

        if (field == null) {
            try {
                field = getField.invoke(clazz, name) as Field
            } catch (e: Exception) {
                // ignore and search next
            }
        }

        if (field == null) {
            try {
                field = getDeclaredField.invoke(clazz, name) as Field
            } catch (e: Exception) {
                // ignore and search next
            }
        }

        if (field != null) {
            if (!field.isAccessible) {
                field.isAccessible = true
            }
            fieldCache[key] = field
            return field
        }

        clazz = clazz.superclass
    }
    throw NoSuchFieldException("Field $name not found in $clazz and super class")
}


val methodCache = HashMap<Int, Method>()
val getMethod: Method = Class::class.java.getDeclaredMethod("getMethod", String::class.java, arrayOfNulls<Class<*>>(0).javaClass)
val getDeclaredMethod: Method = Class::class.java.getDeclaredMethod("getDeclaredMethod", String::class.java, arrayOfNulls<Class<*>>(0).javaClass)

/**
 * Locates a given method anywhere in the class inheritance hierarchy.
 *
 * @param clazz
 * @param name
 * @param paramTypes
 * @param params
 * @return
 */
@Throws(NoSuchMethodException::class)
fun findMethod(clazz: Class<*>, name: String, paramTypes: Array<Class<*>> = arrayOf(), vararg params: Any): Method {
    var targetClazz: Class<*>? = clazz
    while (targetClazz != null) {
        val key: Int = getMethodKey(targetClazz, name, paramTypes)
        var method: Method? = methodCache.get(key)
        //key maybe collision
        if (method != null && name == method.name && paramTypes.size == method.parameterTypes.size) {
            return method
        }
        try {
            method = targetClazz.getDeclaredMethod(name, *paramTypes)
        } catch (e: NoSuchMethodException) {
            // ignore and search next
        }
        if (method == null) {
            try {
                method = targetClazz.getMethod(name, *paramTypes)
            } catch (e: NoSuchMethodException) {
                // ignore and search next
            }
        }
        if (method == null) {
            try {
                method = getDeclaredMethod.invoke(targetClazz, name, *paramTypes) as Method
            } catch (e: java.lang.Exception) {
                // ignore and search next
            }
        }
        if (method == null) {
            try {
                method = getMethod.invoke(targetClazz, name, *paramTypes) as Method
            } catch (e: java.lang.Exception) {
                // ignore and search next
            }
        }
        if (method != null) {
            if (!method.isAccessible) {
                method.isAccessible = true
            }
            methodCache.put(key, method)
            return method
        }
        targetClazz = targetClazz.superclass
    }

    targetClazz = clazz
    while (targetClazz != null) {
        val key: Int = getMethodKey(targetClazz, name, paramTypes)
        val declareMethods = targetClazz.declaredMethods
        val publicMethods = targetClazz.methods
        val allMethods = arrayOfNulls<Method>(declareMethods.size + publicMethods.size)
        System.arraycopy(declareMethods, 0, allMethods, 0, declareMethods.size)
        System.arraycopy(publicMethods, 0, allMethods, declareMethods.size, publicMethods.size)
        outer@ for (method1 in allMethods) {
            if (name != method1!!.name) {
                continue
            }
            val parameterTypes = method1.parameterTypes
            if (parameterTypes.size != params.size) {
                continue
            }
            for (i in parameterTypes.indices) {
                val methodType = parameterTypes[i]
                val paramType: Class<*> = params[i].javaClass
                if (methodType.isAssignableFrom(paramType) || primitiveToWrapper(methodType) == paramType) {
                    if (!method1.isAccessible) {
                        method1.isAccessible = true
                    }
                    methodCache.put(key, method1)
                    return method1
                }
            }
        }
        targetClazz = targetClazz.superclass
    }
    throw NoSuchMethodException("Method $name not found in $clazz and super class")
}

fun getMethodKey(clazz: Class<*>, name: String, paramTypes: Array<Class<*>>?): Int {
    var key = clazz.hashCode() xor name.hashCode()
    if (paramTypes != null && paramTypes.isNotEmpty()) {
        key = key xor paramTypes.size
        for (paramType in paramTypes) {
            key = key xor paramType.hashCode()
        }
    }
    return key
}

val primitiveWrapperMap: MutableMap<Class<*>, Class<*>> by lazy {
    val result = mutableMapOf<Class<*>, Class<*>>()
    result.put(java.lang.Boolean.TYPE, Boolean::class.java)
    result.put(java.lang.Byte.TYPE, Byte::class.java)
    result.put(java.lang.Character.TYPE, Character::class.java)
    result.put(java.lang.Short.TYPE, Short::class.java)
    result.put(java.lang.Integer.TYPE, Integer::class.java)
    result.put(java.lang.Long.TYPE, Long::class.java)
    result.put(java.lang.Double.TYPE, Double::class.java)
    result.put(java.lang.Float.TYPE, Float::class.java)
    result
}


fun primitiveToWrapper(cls: Class<*>?): Class<*>? {
    var convertedClass = cls
    if (cls != null && cls.isPrimitive) {
        convertedClass = primitiveWrapperMap.get(cls)
    }
    return convertedClass
}

/**
 * 执行指定的方法
 *
 * @param name     方法名称
 * @param params   参数值
 * @return
 */
@SuppressWarnings("unchecked")
fun <T> Any.invoke(name: String, vararg params: Any): T? {
    try {
        val method: Method = findMethod(this.javaClass, name, toType(*params), *params)
        return method.invoke(this, *params) as? T
    } catch (e: java.lang.Exception) {
        Log.e("reflect","invoke error: ${this}.$name(${params.contentToString()})", e)
    }
    return null
}

/**
 * 执行指定的方法
 *
 * @param name     方法名称
 * @param params   参数值
 * @return
 */
@SuppressWarnings("unchecked")
fun <T> Class<*>.invoke(name: String, vararg params: Any): T? {
    try {
        val method: Method = findMethod(this, name, toType(*params), *params)
        return method.invoke(null, *params) as? T
    } catch (e: java.lang.Exception) {
        Log.e("reflect","invoke error: ${this}.$name(${params.contentToString()})", e)
    }
    return null
}

fun toType(vararg params: Any): Array<Class<*>> {
    return params.map { it.javaClass }.toTypedArray()
}

fun Class<*>.getObjMethods(): HashMap<Int, Method> {
    val map = HashMap<Int, Method>()
    val methods = this.declaredMethods
    for (item in methods) {
        val key: Int = getMethodKey(this, item.name, item.parameterTypes)
        map[key] = item
    }
    return map
}

/**
 * 加载暂定名称的Class, 如果Class找不到则返回null
 */
fun Any.forName(className: String): Class<*>? {
    return try {
        Class.forName(className)
    } catch (e: Exception) {
        Log.e("reflect","无法加载类名为$className",e)
        null
    }
}